/*
 *  KnowledgeBase.c
 *  IncrementalAlgorithmSerial
 *
 *  Created by Roman Kutlak on 27/03/2010.
 *  Copyright 2010 Roman Kutlak. All rights reserved.
 *
 */

#include <iterator>

#include "KnowledgeBase.h"
#include "Ontology.h"
#include "Entity.h"
#include "Property.h"

using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
using std::string;

namespace kutlak {
namespace roman {
namespace gre {
    
    
    // class implemented
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
    pKnowledgeBase KnowledgeBase::sDefault = pKnowledgeBase();

    
#pragma mark -
#pragma mark lifecycle
    //=============================  LIFECYCLE  ================================ 
    KnowledgeBase::KnowledgeBase(const string& fileName) 
    {
        if ("" != fileName)
            AddFile(fileName);
    }
    
    KnowledgeBase::KnowledgeBase(const std::vector<EntityPtrT>& entities) 
    {
        for (size_t i = 0; i < entities.size(); ++i)
            mEntities[entities[i]->Name()] = entities[i];
    }
    
    KnowledgeBase::KnowledgeBase(const KnowledgeBase& src) :
    mEntities(src.mEntities) {}
    
    KnowledgeBase::KnowledgeBase(KnowledgeBase&& src) noexcept(true) :
    mEntities(std::move(src.mEntities)) {}
    
    KnowledgeBase::~KnowledgeBase() noexcept(true) = default;
    
    KnowledgeBase& KnowledgeBase::operator=(KnowledgeBase rhs) noexcept(true) 
    {
        swap(*this, rhs);
        return *this;
    }
    
    void swap(KnowledgeBase& lhs, KnowledgeBase& rhs) noexcept(true) 
    {
        using std::swap;
        swap(rhs.mEntities, lhs.mEntities);
    }
    
    void KnowledgeBase::SetDefault(const pKnowledgeBase& kb) 
    {
        sDefault = kb;
    }
    
    pKnowledgeBase KnowledgeBase::Default() 
    {
        return sDefault;
    }
    
#pragma mark -
#pragma mark operators
    //=============================  OPERATORS  ================================
    bool KnowledgeBase::operator==(const KnowledgeBase& rhs) const 
    {
        if (this == &rhs)
            return true;
        
        if (mEntities.size() != rhs.mEntities.size())
            return false;
        
        for (const auto& pp : mEntities) 
        {
            EntityPtrT e = rhs.EntityPtrByName(pp.first);
            if (!e || *e != *pp.second)
                return false;
        }
        
        return true;
    }
    
    bool KnowledgeBase::operator!=(const KnowledgeBase& rhs) const 
    {
        return !(*this == rhs);
    }
    
#pragma mark -
#pragma mark operations
    //=============================  OPERATIONS  ===============================
    
    void KnowledgeBase::AddEntity(const Entity& entity) 
    {
        mEntities.insert(make_pair(entity.Name(), EntityPtrT(new Entity(entity))));
    }
    
    void KnowledgeBase::RemoveEntity(const Entity& entity) 
    {
        iterator it = mEntities.find(entity.Name());
        if (it != mEntities.end())
            mEntities.erase(it);
    }
    
    EntityPtrT KnowledgeBase::EntityPtrById(int entityId) const 
    {
        const_iterator it;
        for (it = begin(); it != end(); ++it) 
        {
            if (entityId == it->second->Id())
                return it->second;
        }
        
        return EntityPtrT();
    }
    
    EntityPtrT KnowledgeBase::EntityPtrByName(const string& name) const 
    {
        const_iterator it;
        it = mEntities.find(name);
        if (end() != it)
            return it->second;
        
        return EntityPtrT();
    }
    
    void KnowledgeBase::Print(int number) const 
    {
        if (mEntities.empty()) 
        {
            cout << "The knowledgebase does not contain any data." << endl;
            return;
        }
        
        bool stop = false;
        
        if (-1 < number) { // we need to stop when we print number statements.
            stop = true;
        }
        
        KnowledgeBase::const_iterator it;
        for (it = begin(); it != end(); ++it) 
        {
            cout << it->first << " type: " << it->second->Type().Value() << endl;
            cout << it->second->ToString() << endl;
            if (stop && --number < 0)
                break;
        }
    }
    
    void KnowledgeBase::Clear() 
    {
        mEntities.clear();
    }
    
#pragma mark -
#pragma mark access
    //=============================  ACESS       ===============================
    
    size_t KnowledgeBase::Size() const 
    {
        return mEntities.size();
    }
    
    std::vector<EntityPtrT> KnowledgeBase::Entities() const 
    {
        const_iterator it = begin();
        const_iterator e = end();
        std::vector<EntityPtrT> result;
        typedef std::back_insert_iterator<std::vector<EntityPtrT> > inserter_t;
        inserter_t inserter = std::back_inserter(result);
        
        for (; it != e; ++it) 
        {
            inserter = it->second;
        }
        
        return result;
    }
    
    std::vector<EntityPtrT> KnowledgeBase::EntitiesOfType(const Property& type) const 
    {
        std::vector<EntityPtrT> result;
        typedef std::back_insert_iterator<std::vector<EntityPtrT> > inserter_t;
        inserter_t inserter = std::back_inserter(result);
        
        for (const auto& entPair : mEntities) 
        {
            if (entPair.second->IsType(type))
                inserter = entPair.second;
        }
        
        return result;    
    }
    
    std::vector<EntityPtrT> KnowledgeBase::Entities(const Property& pattern) const 
    {
        const_iterator it = begin();
        const_iterator e = end();
        std::vector<EntityPtrT> result;
        typedef std::back_insert_iterator<std::vector<EntityPtrT> > inserter_t;
        inserter_t inserter = std::back_inserter(result);
        
        for (; it != e; ++it) 
        {
            if (it->second->HasProperty(pattern))
                inserter = it->second;
        }
        
        return result;    
    }
    
#pragma mark -
#pragma mark Files
    //=============================  FILE ACCESS  ===============================
    
    ostream& operator<<(ostream& os, const KnowledgeBase& obj) 
    {
        obj.Serialise(os);
        return os;
    }
    
    istream& operator>>(istream& is, KnowledgeBase& obj) 
    {
        KnowledgeBase tmp;
        tmp.Deserialise(is);
        swap(obj, tmp);
        return is;
    }
    
    void KnowledgeBase::Serialise(ostream& os) const 
    {
        os << "<knowledgebase>\n";
        for (const auto& pair : mEntities) 
        {
            const Entity& e = *pair.second;
            os << e;
        }
        os << "</knowledgebase>\n";
    }
    
    void KnowledgeBase::Deserialise(istream& is) 
    {
        string tmp;
        if (!(is >> tmp) || "<knowledgebase>" != tmp) 
        {
            string message = "Expected <knowledgebase>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        try 
        {
            Entity e;
            while (is >> e)
                mEntities.emplace(string(e.Name()),
                                  std::make_shared<Entity>(std::move(e)));
        } 
        catch (DeserialisationException& de) 
        {
            if ("</knowledgebase>" != de.data()) 
            {
                string message = "Exception while deserialising KnowledgeBase: ";
                throw DeserialisationException(message + de.what(),
                                               std::move(de.data()));
            }
        }
    }
    
    void KnowledgeBase::AddFile(const string& fileName, bool createNewEntities) 
    {
#ifdef KNOWLEDGEBASE_DEBUG
        cerr << "calling KnowledgeBase::AddFile()" << endl;
#endif
        
        // open the files
        ifstream input;
        input.open(fileName.c_str());
        
        if (!input.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the input file ";
            cerr << fileName << endl;
            return;
        }
        
        FromRdf(input, createNewEntities);
        
        // save buffer
        input.close();
    }
    
    void KnowledgeBase::FromRdf(istream& input, bool createNewEntities) 
    {
#ifdef KNOWLEDGEBASE_DEBUG
        cerr << "calling KnowledgeBase::AddFile(istream)" << endl;
#endif
        
        string delimiter(" ");
        
        mEntities.max_load_factor(0.75);
        mEntities.reserve(1000);
        
        string name;
        string attribute;
        string value;
        
        while (input >> name >> attribute && getline(input, value)) 
        {
            // lines starting # are treated as comments
            if (!name.empty() && name.front() == '#')
                continue;
            
            
            if (attribute.empty() || value.empty()) 
            {
#ifdef KNOWLEDGEBASE_DEBUG
                cerr << __FILE__ << " (" << __LINE__ << "): ";
#endif
                continue;
            }
            
            
            // value ends with " ."
            size_t pos = value.rfind(".");
            if (string::npos != pos)
                value.resize(pos);
            value = Trim(value, false);
            
            if (name.front() == '<')
                name = name.substr(1);
            if (name.back() == '>')
                name.pop_back();
            
            if (attribute.front() == '<')
                attribute = attribute.substr(1);
            if (attribute.back() == '>')
                attribute.pop_back();
            
            if (value.front() == '<')
                value = value.substr(1);
            if (value.back() == '>')
                value.pop_back();
            
            name.shrink_to_fit();
            attribute.shrink_to_fit();
            value.shrink_to_fit();
            
#ifdef KNOWLEDGEBASE_DEBUG
            
            {
                cerr << __FILE__ << " (" << __LINE__ << "): \n";
                cerr << "\tname:" << name << endl;
                cerr << "\tattribute:" << attribute << endl;
                cerr << "\tvalue:" << value << endl;
            }
#endif
            KnowledgeBase::iterator it = mEntities.find(name);
            if (it == mEntities.end()) // entity not found
            {
                if (createNewEntities) 
                {
                    EntityPtrT entity(new Entity(name));
                    entity->AddProperty(Property(attribute, value));
                    entity->AddProperty(Ontology::kThingType);
                    mEntities.emplace(name, entity);
                }
            }
            else 
            { // entity exists
                it->second->AddProperty(Property(attribute, value));
            } // end entity found (else part of an if statement)
            
            
            // check if the current property is a relation and if so add it
            // to the relatum as an inverse property
            it = mEntities.find(value);
            if (it != mEntities.end()) 
            {
                Property p(std::move(attribute),
                           std::move(name));
                p.SetInverse(true);
                it->second->AddProperty(std::move(p));
            }
            
        } // end while reading from a file

        mEntities.max_load_factor(1.0);
        mEntities.rehash(mEntities.size());
    }

    
    void KnowledgeBase::SaveFile(const string& fileName) const 
    {
        // open the files
        ofstream output;
        output.open(fileName.c_str());
        
        if (!output.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the output file ";
            cerr << fileName << endl;
            return;
        }
        
        ToRdf(output);
        
        output.close();        
    }
    
    void KnowledgeBase::ToRdf(ostream& output) const 
    {
        for (const auto& pp : mEntities) 
        {
            const Entity& e = *pp.second;
            e.ToRdf(output);
        }
    }
}
}
}
